/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       hero_class_properties.h

	$Header: /game/hero_class_properties.h $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#pragma warning( disable: 4786 ) // Disable the ever so helpful "identifier was truncated to '255' characters" warning

#include "hero_class_properties.h"

#include <assert.h>
#include "alignment_relationship.h"
#include "elements_of.h"
#include "enum_map.h"
#include "enum_operations.h"
#include "external_string.h"
#include "skill_properties.h"
#include "table.h"
#include "table_ptr.h"
#include "town_type.h"

#define DECLARE_KEYWORD( name ) k_hero_class_##name, #name

namespace
{
	char const* k_class_name[k_hero_class_count];

	struct t_class_data
	{
		t_hero_class hero_class;
		char const*  keyword;
		t_town_type  alignment;
		t_skill_type skills[5];
	};

	t_class_data const k_data[] =
	{
		{ DECLARE_KEYWORD( knight ),	k_town_life,	k_skill_tactics, k_skill_defense,
														k_skill_none },
		{ DECLARE_KEYWORD( priest ),	k_town_life,	k_skill_life_magic,
		                                                k_skill_healing_magic, 
														k_skill_none },
		{ DECLARE_KEYWORD( lord ),		k_town_order,	k_skill_nobility, 
														k_skill_estates, k_skill_none },
		{ DECLARE_KEYWORD( mage ),		k_town_order,	k_skill_order_magic, 
														k_skill_enchantment, 
														k_skill_none },
		{ DECLARE_KEYWORD( death_knight ),	k_town_death,	k_skill_tactics,
														k_skill_offense,
														k_skill_none },
		{ DECLARE_KEYWORD( necromancer ),	k_town_death,	k_skill_death_magic,
															k_skill_black_magic,
															k_skill_none },	
		{ DECLARE_KEYWORD( thief ),			k_town_chaos,	k_skill_scouting,
															k_skill_stealth,
															k_skill_none },
		{ DECLARE_KEYWORD( sorcerer ),		k_town_chaos,	k_skill_chaos_magic,
															k_skill_conjuration,
															k_skill_none },
		{ DECLARE_KEYWORD( archer ),		k_town_nature,	k_skill_combat_defense,
															k_skill_archery,
															k_skill_none },
		{ DECLARE_KEYWORD( druid ),			k_town_nature,	k_skill_nature_magic,
															k_skill_druid_magic,
															k_skill_none },
		{ DECLARE_KEYWORD( barbarian ),		k_town_might,	k_skill_combat_defense,
															k_skill_melee,
															k_skill_magic_resistance,
															k_skill_none },

		{ DECLARE_KEYWORD( general ),		k_town_type_count,	k_skill_tactics,
																k_skill_combat_defense,
																k_skill_none },
		{ DECLARE_KEYWORD( field_marshal ),	k_town_type_count,	k_skill_tactics,
																k_skill_scouting,
																k_skill_none },
		{ DECLARE_KEYWORD( lord_commander ),k_town_type_count,	k_skill_tactics,
																k_skill_nobility,
																k_skill_none },
		{ DECLARE_KEYWORD( crusader ),		k_town_life,		k_skill_tactics,
																k_skill_life_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( illusionist ),	k_town_order,		k_skill_tactics,
																k_skill_order_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( reaver ),		k_town_death,		k_skill_tactics,
																k_skill_death_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( pyromancer ),	k_town_chaos,		k_skill_tactics,
																k_skill_chaos_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( warden ),		k_town_nature,		k_skill_tactics,
																k_skill_nature_magic,
																k_skill_none },

		{ DECLARE_KEYWORD( ranger ),		k_town_type_count,	k_skill_combat_defense, 
																k_skill_scouting, k_skill_none },
		{ DECLARE_KEYWORD( warlord ),		k_town_type_count,	k_skill_combat_defense, 
																k_skill_nobility, k_skill_none },
		{ DECLARE_KEYWORD( paladin ),		k_town_life,		k_skill_combat_defense, 
																k_skill_life_magic, k_skill_none },
		{ DECLARE_KEYWORD( battle_mage ),	k_town_order,		k_skill_combat_defense, 
																k_skill_order_magic, k_skill_none },
		{ DECLARE_KEYWORD( assassin ),		k_town_death,		k_skill_combat_defense,
																k_skill_death_magic, k_skill_none },
		{ DECLARE_KEYWORD( fireguard ),		k_town_chaos,		k_skill_combat_defense, 
																k_skill_chaos_magic, k_skill_none },
		{ DECLARE_KEYWORD( green_knight ),	k_town_nature,		k_skill_combat_defense,
																k_skill_nature_magic,
																k_skill_none },

		{ DECLARE_KEYWORD( guildmaster ),	k_town_type_count,	k_skill_scouting, 
																k_skill_nobility, k_skill_none },
		{ DECLARE_KEYWORD( prophet ),		k_town_life,		k_skill_scouting,
																k_skill_life_magic, k_skill_none },
		{ DECLARE_KEYWORD( seer ),			k_town_order,		k_skill_scouting,
																k_skill_order_magic, k_skill_none },
		{ DECLARE_KEYWORD( ninja ),			k_town_death,		k_skill_scouting,
																k_skill_death_magic, k_skill_none },
		{ DECLARE_KEYWORD( fire_diviner ),	k_town_chaos,		k_skill_scouting,
																k_skill_chaos_magic, k_skill_none },
		{ DECLARE_KEYWORD( bard ),			k_town_nature,		k_skill_scouting,
																k_skill_nature_magic,
																k_skill_none },

		{ DECLARE_KEYWORD( cardinal ),		k_town_life,		k_skill_nobility, 
																k_skill_life_magic, k_skill_none },
		{ DECLARE_KEYWORD( wizard_king ),	k_town_order,		k_skill_nobility,
																k_skill_order_magic, k_skill_none },
		{ DECLARE_KEYWORD( dark_lord ),		k_town_death,		k_skill_nobility,
																k_skill_death_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( witch_king ),	k_town_chaos,		k_skill_nobility,
																k_skill_chaos_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( beast_lord ),	k_town_nature,		k_skill_nobility,
																k_skill_nature_magic,
																k_skill_none },

		{ DECLARE_KEYWORD( monk ),			k_town_life,		k_skill_life_magic,
																k_skill_order_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( dark_priest ),	k_town_death,		k_skill_life_magic,
																k_skill_death_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( heretic ),		k_town_chaos,		k_skill_life_magic,
																k_skill_chaos_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( summoner ),		k_town_nature,		k_skill_life_magic,
																k_skill_nature_magic,
																k_skill_none },

		{ DECLARE_KEYWORD( shadow_mage ),	k_town_death,		k_skill_order_magic,
																k_skill_death_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( wizard ),		k_town_order,		k_skill_order_magic,
																k_skill_chaos_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( enchanter ),		k_town_order,		k_skill_order_magic,
																k_skill_nature_magic,
																k_skill_none },

		{ DECLARE_KEYWORD( lich ),			k_town_chaos,		k_skill_death_magic,
																k_skill_chaos_magic,
																k_skill_none },
		{ DECLARE_KEYWORD( demonologist ),	k_town_death,		k_skill_death_magic,
																k_skill_nature_magic,
																k_skill_none },

		{ DECLARE_KEYWORD( warlock ),		k_town_chaos,		k_skill_chaos_magic,
																k_skill_nature_magic,
																k_skill_none },

		{ DECLARE_KEYWORD( archmage ),		k_town_type_count,	k_skill_life_magic, 
																k_skill_order_magic,
																k_skill_death_magic,
																k_skill_chaos_magic,
																k_skill_nature_magic },

	};

	class t_hero_class_keyword_list : public std::vector<t_char_ptr_pair>
	{
	public:
		t_hero_class_keyword_list();
	};

	typedef t_enum_map<t_hero_class> t_hero_class_map;

	struct t_class_properties
	{
		t_town_type				alignment;
		char const*				keyword;
		std::vector<t_skill>	skills;
		std::string				name;
		std::string				description;

		t_class_properties() {}
		t_class_properties( t_class_data const& arg ); 
	};

	// ---------------------------------------------------------------
	// table of skill weighting, by hero class
	// ---------------------------------------------------------------
	class t_skill_weight_table
	{
	public:
		t_skill_weight_table();

		int get_weight( t_hero_class hero_class, t_skill_type skill ) const;
	protected:
		int m_weight[k_hero_class_count][k_skill_count];
	};

	inline int t_skill_weight_table::get_weight( t_hero_class hero_class, t_skill_type skill ) const
	{
		return m_weight[hero_class][skill];
	}

	t_class_properties k_class_properties[k_hero_class_count];
}; // unnamed namespace

t_class_properties::t_class_properties( t_class_data const& arg ) 
				  : name( t_external_string( arg.keyword ) ),
				    description( t_external_string( std::string( "description_" ) + arg.keyword ) )
{
	int     i;
	t_skill skill;

	alignment = arg.alignment;
	keyword = arg.keyword;
	for (i = 0; i < ELEMENTS_OF( arg.skills ); i++)
	{
		if (arg.skills[i] == k_skill_none )
			break;
		skill.skill = arg.skills[i];
		skill.level = k_mastery_basic;
		skills.push_back( skill );
	}
}

// initialize k_class_properties
static void set_data()
{
	static bool initialized = false;

	if (initialized)
		return;
	initialized = true;

	int i;

	for (i = 0; i < ELEMENTS_OF( k_data ); i++)
	{
		t_class_data const& data = k_data[i];

		k_class_properties[data.hero_class] = data;
	}
}

// ---------------------------------------------------------------
// vector mapping hero classes to char pointers
// ---------------------------------------------------------------
t_hero_class_keyword_list::t_hero_class_keyword_list()
{
	int i;

	reserve( ELEMENTS_OF( k_data ));
	for (i = 0; i < ELEMENTS_OF( k_data ); i++)
	{
		t_char_ptr_pair pair;

		pair.value = k_data[i].hero_class;
		pair.keyword = k_data[i].keyword;
		push_back( pair );
	}
}


// ---------------------------------------------------------------
// get an initialized vector of keyword pairs
// ---------------------------------------------------------------
static t_hero_class_keyword_list const& get_keyword_list()
{
	static t_hero_class_keyword_list const list;

	return list;
}

// ---------------------------------------------------------------
// get a map of hero classes / strings
// ---------------------------------------------------------------
static t_hero_class_map const& get_map()
{
	static t_hero_class_map const map( &(*get_keyword_list().begin()), get_keyword_list().size(), 
		                               k_hero_class_count );
	return map;
}

// ---------------------------------------------------------------
// get hero class from a string
// ---------------------------------------------------------------
t_hero_class get_hero_class( std::string const& keyword )
{
	return get_map()[keyword];
}

// ---------------------------------------------------------------
// table of skill weighting, by hero class
// ---------------------------------------------------------------
t_skill_weight_table::t_skill_weight_table()
{
	t_table_cache				cache( "skill_weights" );
	t_table_ptr					table = cache.get();
	std::vector<t_skill_type>	columns;
	t_table::const_iterator     line = table->begin();
	t_skill_type                skill;
	t_hero_class                hero_class;
	int                         i;

	memset( m_weight, 0, sizeof( m_weight ));
	// determine types of columns
	assert( line->size() > 1 );
	columns.reserve( line->size() - 1 );
	for (i = 1; i < (int)line->size(); i++)
	{
		skill = get_skill_type( (*line)[i] );
		columns.push_back( skill );
	}
	line++;

	for (; line != table->end(); line++)
	{
		hero_class = get_hero_class( (*line)[0] );
		if (hero_class == k_hero_class_none)
			continue;

		for (i = 1; i < (int)line->size() && i < (int)columns.size() + 1; i++)
		{
			skill = columns[i - 1];
			if (skill == k_skill_none)
				continue;
			m_weight[hero_class][columns[i - 1]] = atoi( (*line)[i].c_str() );
		}
	}
}



// get hiring costs for various classes
void get_hero_hiring_cost( t_town_type alignment, int cost[k_hero_class_basic_count] )
{
	t_hero_class hero_class;
	
	set_data();
	if (alignment == k_town_none)
	{
		for (hero_class = t_hero_class(0); hero_class < k_hero_class_basic_count; enum_incr( hero_class ))
			cost[hero_class] = 2000;
		return;
	}
	if (alignment == k_town_might)
	{
		for (hero_class = t_hero_class(0); hero_class < k_hero_class_basic_count; enum_incr( hero_class ))
		{
			if (is_spellcaster( hero_class ))
				cost[hero_class] = 0;
			else
				cost[hero_class] = 2500;
		}
		cost[k_hero_class_barbarian] = 1500;
		return;
	}

	for (hero_class = t_hero_class(0); hero_class < k_hero_class_basic_count; enum_incr( hero_class ))
	{
		t_town_type hero_alignment = k_class_properties[hero_class].alignment;

		switch (get_alignment_relationship( alignment, hero_alignment ))
		{
			case k_alignments_identical:
				cost[hero_class] = 1500;
				break;

			case k_alignments_allied:
				cost[hero_class] = 2000;
				break;

			case k_alignments_neutral:
				cost[hero_class] = 0;
				break;

			case k_alignments_hostile:
				cost[hero_class] = 0;
				break;
		}
	}
}

// get alignment of a starting class
t_town_type get_alignment( t_hero_class hero_class )
{
	assert( hero_class < k_hero_class_basic_count );
	set_data();

	return k_class_properties[hero_class].alignment;
}

// get starting skills for a starting class
std::vector<t_skill> const& get_starting_skills( t_hero_class hero_class )
{
	set_data();

	return k_class_properties[hero_class].skills;
}

std::string get_class_name( t_hero_class hero_class )
{
	set_data();
	return k_class_properties[hero_class].name;
}

std::string get_keyword( t_hero_class hero_class )
{
	set_data();
	return k_class_properties[hero_class].keyword;
}

std::string get_class_description( t_hero_class hero_class )
{
	set_data();
	return k_class_properties[hero_class].description;
}

// --------------------------------------------------------------------------------
// get the weighting for a skill for a hero class.
// --------------------------------------------------------------------------------
int get_skill_weight( t_hero_class hero_class, t_skill_type skill )
{
	static t_skill_weight_table const table;

	return table.get_weight( hero_class, skill );
}

static int const k_class_weight_factor[] = { 0, 1, 3, 3, 3 };

// --------------------------------------------------------------------------------
// determine the value of a given hero class, given a set of skills.
// --------------------------------------------------------------------------------
int get_class_weight( t_hero_class hero_class, std::vector<t_skill> const& skills )
{
	set_data();

	t_class_properties const& properties = k_class_properties[hero_class];
	int                       weights[k_skill_primary_count];
	int						  i;
	int                       result = 0;
	int                       skill_count = 0;

	memset( weights, 0, sizeof( weights ));
	for (i = 0; i < (int)skills.size(); i++)
		weights[get_primary_skill( skills[i].skill )] += skills[i].level + 1;
	for (i = 0; i < (int)properties.skills.size(); i++)
	{
		if (properties.skills[i].skill >= k_skill_primary_count)
			continue;
		skill_count++;
		result += weights[properties.skills[i].skill];
	}
	result -= k_class_weight_factor[skill_count - 1];
	return result;
}

// --------------------------------------------------------------------------------
// determine the hero class with the highest value, given a set of skills.
// --------------------------------------------------------------------------------
t_hero_class get_best_class( std::vector<t_skill> const& skills )
{
	set_data();

	int                       weights[k_skill_primary_count];
	int						  i;
	int                       highest = 0;
	t_hero_class              result = k_hero_class_none;
	t_hero_class			  hero_class;
	int                       total;
	int                       skill_count;

	memset( weights, 0, sizeof( weights ));
	for (i = 0; i < (int)skills.size(); i++)
		weights[get_primary_skill( skills[i].skill )] += skills[i].level + 1;

	for (hero_class = t_hero_class(0); hero_class < k_hero_class_count; enum_incr( hero_class ))
	{
		t_class_properties const& properties = k_class_properties[hero_class];
		total = 0;
		skill_count = 0;
		for (i = 0; i < (int)properties.skills.size(); i++)
		{
			if (properties.skills[i].skill >= k_skill_primary_count)
				continue;
			skill_count++;
			total += weights[properties.skills[i].skill];
		}
		total -= k_class_weight_factor[skill_count - 1];
		if (total < highest)
			continue;
		highest = total;
		result = hero_class;
	}
	return result;
}

// --------------------------------------------------------------------------------
// tell whether a hero class is considered primarily a spellcasting class
// --------------------------------------------------------------------------------
bool is_spellcaster( t_hero_class hero_class )
{
	static bool initialized = false;
	static bool spellcasters[k_hero_class_count];
	static const t_hero_class spellcaster_list[] =
	{
		k_hero_class_priest,
		k_hero_class_mage,
		k_hero_class_necromancer,
		k_hero_class_sorcerer,
		k_hero_class_druid,

		k_hero_class_prophet,
		k_hero_class_cardinal,
		k_hero_class_monk,
	
		k_hero_class_illusionist,
		k_hero_class_battle_mage,
		k_hero_class_seer,
		k_hero_class_wizard_king,
		k_hero_class_wizard,
		k_hero_class_enchanter,

		k_hero_class_dark_priest,
		k_hero_class_shadow_mage,
		k_hero_class_demonologist,

		k_hero_class_pyromancer,
		k_hero_class_fire_diviner,
		k_hero_class_witch_king,
		k_hero_class_heretic,
		k_hero_class_lich,
		k_hero_class_warlock,

		k_hero_class_summoner,
		k_hero_class_druid,
	
		k_hero_class_archmage,
	};

	if (!initialized)
	{
		int i;

		initialized = true;
		memset( spellcasters, 0, sizeof( spellcasters ));
		for (i = 0; i < ELEMENTS_OF( spellcaster_list ); i++)
			spellcasters[spellcaster_list[i]] = true;
	}
	return spellcasters[hero_class];
}

// --------------------------------------------------------------------------------
// return a standardized keyword based on town type, sex, and spellcasting
// --------------------------------------------------------------------------------
static char const* const sex_keywords[] = { "female", "male" };

std::string  get_type_keyword( t_town_type alignment, bool is_male, bool spellcaster )
{
	static char const* const type_keywords[] = { "might", "magic" };

	std::string result;

	result += k_town_keyword[ alignment ];
	result += '_';
	result += type_keywords[ spellcaster ];
	result += '_';
	result += sex_keywords[ is_male ];
	return result;
}

// ------------------------------------------------------------------------------
// construct array of hero caches
// ------------------------------------------------------------------------------
std::string get_combat_type_keyword( t_town_type alignment, t_hero_combat_model_type type,
									 bool is_male )
{
	static char const* type_keywords[] = { "fighter", "archer", "mage" };
	std::string       name;

	name = "hero.";
	name += k_town_keyword[alignment];
	name += "_";
	name += type_keywords[type];
	name += "_";
	name += sex_keywords[is_male];
	return name;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int compute_default_experience_level(
	t_hero_class					start_class,
	std::vector< t_skill > const &	skills )
{
	assert( start_class >= 0 && start_class < k_hero_class_basic_count );

	typedef std::vector< t_skill > t_skill_vector;

	int result = 1;

	t_skill_vector::const_iterator skill_end = skills.end();
	t_skill_vector::const_iterator skill_iter = skills.begin();
	for ( ; skill_iter != skill_end; ++skill_iter )
		result += skill_iter->level - k_mastery_none;

	t_skill_vector const & starting_skills = get_starting_skills( start_class );
	skill_end = starting_skills.end();
	skill_iter = starting_skills.begin();
	for ( ; skill_iter != skill_end; ++skill_iter )
		result -= skill_iter->level - k_mastery_none;

	assert( result >= 1 );

	if ( result > k_max_experience_level )
		return k_max_experience_level;

	return result;
}
